﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GridRegulator;
using NoahSolver;


namespace NoahHelper
{
    using Win32;

    public partial class MainForm : Form
    {
        enum State
        {
            Idle,
            Done,
            NoSolution,
            Failed,
            Disabled,
        }

        Bitmap mClipboardImage = null;
        Bitmap mSolutionImage = null;
        Bitmap mDisplayBuffer = null;
        IssueHandler mHandler = null;
        Solver.Solutions mSolutions = null;
        TileMatcher mTileMatcher = null;
        int mSolIndex = 0;
        State mState;

        /* program registers */
        int mNewWidth = -1;
        int mNewHeight = -1;

        /* windows clipboard facilities */
        IntPtr mClipboardViewerNext;
        bool mClipboardRegistered = false;


        /* settings */
        const int kFrameBorderWidth = 5;
        int kIniWidth = 300;
        int kIniHeight = 300;
        bool mResponsiveToMouse = OptionsForm.kDefMouseEnabled;


        int TopBarHeight
        {
            get
            {
                return this.NoahMenuStrip.Height;
            }
        }

        int BottomBarHeight
        {
            get
            {
                return this.NoahStatusStrip.Height;
            }
        }

    /* Constructor / Destructor */

        public MainForm()
        {
            InitializeComponent();
            mState = State.Idle;
            mClipboardRegistered = false;
        }

        private delegate void UpdateControlsDelegate();

        private void UpdateControls()
        {
            if (this.InvokeRequired)
            {
                UpdateControlsDelegate del = new UpdateControlsDelegate(UpdateControls);
                this.Invoke(del);
                return;
            }

            bool bToInvalidate = false;

            if (mNewWidth > 0)
            {
                bToInvalidate = (this.Width != mNewWidth || this.Height != mNewHeight);
                this.Width = mNewWidth;
                this.Height = mNewHeight;
                mNewWidth = -1;
                mNewHeight = -1;
            }

            switch (mState)
            {
                case State.Idle:
                    MainStatusLabel.Text = "Ready";
                    break;
                case State.Failed:
                    MainStatusLabel.Text = "Failed";
                    break;
                case State.Disabled:
                    MainStatusLabel.Text = "Disabled";
                    break;
                case State.Done:
                    MainStatusLabel.Text = String.Format("Solution {0}/{1}",
                        mSolIndex + 1, mSolutions.Count);
                    break;
            }

            if (mState == State.Done && mSolutions != null && mSolutionImage != null)
            {
                if (mSolutions.Count > 1)
                {
                    NextSolutionToolStripMenuItem.Enabled = (mSolIndex < mSolutions.Count - 1);
                    PrevSolutionToolStripMenuItem.Enabled = (mSolIndex > 0);
                }
                else
                {
                    NextSolutionToolStripMenuItem.Enabled = false;
                    PrevSolutionToolStripMenuItem.Enabled = false;
                }

                if (!this.MaximizeBox)
                {
                    this.MaximizeBox = true;
                    this.FormBorderStyle = FormBorderStyle.Sizable;
                }

                FirstSolutionToolStripMenuItem.Enabled
                    = (mSolutions.Count > 0 && mSolIndex > 0);
            }
            else
            {
                FirstSolutionToolStripMenuItem.Enabled = false;
                PrevSolutionToolStripMenuItem.Enabled = false;
                NextSolutionToolStripMenuItem.Enabled = false;

                if (this.MaximizeBox)
                {
                    this.MaximizeBox = false;
                    this.FormBorderStyle = FormBorderStyle.FixedSingle;
                }
                if (this.Width != kIniWidth || this.Height != kIniHeight)
                {
                    this.Width = kIniWidth;
                    this.Height = kIniHeight;
                    bToInvalidate = true;
                }
            }

            if (bToInvalidate)
            {
                Invalidate();
            }
        }

        void RegisterClipboardViewer()
        {
            if (mClipboardRegistered)
            {
                return;
            }
            mClipboardViewerNext = User32.SetClipboardViewer(this.Handle);
            mClipboardRegistered = true;
        }

        void UnregisterClipboardViewer()
        {
            if (!mClipboardRegistered)
            {
                return;
            }
            User32.ChangeClipboardChain(this.Handle, mClipboardViewerNext);
            mClipboardRegistered = false;
        }

        void Serve()
        {
            if (mState == State.Disabled)
            {
                return;
            }
            if (Clipboard.ContainsImage())
            {
                // An image is available;
                mClipboardImage = Clipboard.GetImage() as Bitmap;

                if (mClipboardImage != null)
                {
                    // process the image and solve the problem
                    // ...

                    System.Diagnostics.Trace.WriteLine("AddIssue()");

                    mHandler.AddIssue(mClipboardImage, new IssueHandler.OnFinishCallback(OnSolved));
                }
            }
            else
            {
                mClipboardImage = null;
            }
        }


        void OnSolved(Solver.Solutions sols, TileMatcher tm, Bitmap src)
        {
            mSolutions = sols;
            mTileMatcher = tm;

            mSolIndex = 0;
            if (sols != null)
            {
                if (sols.Count > 0)
                {
                    int minw = kFrameBorderWidth * 3;
                    if (tm.TileHeight > minw && tm.TileWidth > minw)
                    {
                        mSolutionImage = mHandler.Reconstruct(sols, tm, src);

                        int dw = this.Width - this.ClientSize.Width;
                        int dh = this.Height - this.ClientSize.Height + this.TopBarHeight + this.BottomBarHeight;

                        if (tm != null)
                        {
                            mNewWidth = Math.Min(800, dw + tm.N * tm.TileWidth);
                            mNewHeight = Math.Min(600, dh + tm.M * tm.TileHeight);
                        }
                        else
                        {
                            mNewWidth = kIniWidth;
                            mNewHeight = kIniHeight;
                        }
                        mState = State.Done;
                    }
                    else
                    {
                        mState = State.Failed;
                    }
                }
                else
                {
                    mState = State.NoSolution;
                }
            }
            else
            {
                mState = State.Failed;
            }

            System.Diagnostics.Trace.WriteLine("Update from Onsolved()");

            UpdateControls();
        }

        private void GetSolTilePair(int isol, out Rectangle rect1, out Rectangle rect2)
        {
            int x0, y0, x1, y1, w, h;
            GetSolTilePair(isol, out x0, out y0, out x1, out y1, out w, out h);
            rect1 = new Rectangle(x0, y0, w, h);
            rect2 = new Rectangle(x1, y1, w, h);
        }

        private void GetSolTilePair(int isol, out int x0, out int y0, out int x1, out int y1, 
            out int w, out int h)
        {
            Solver.Solution sol = mSolutions[isol];

            w = mTileMatcher.TileWidth;
            h = mTileMatcher.TileHeight;
            x0 = sol.J0 * w;
            y0 = sol.I0 * h + TopBarHeight;
            x1 = sol.J1 * w;
            y1 = sol.I1 * h + TopBarHeight;
        }


        private void GotoNextSolution()
        {
            if (mSolutionImage == null || mSolutions == null || mSolutions.Count == 0)
            {
                FirstSolutionToolStripMenuItem.Enabled = false;
                NextSolutionToolStripMenuItem.Enabled = false;
                PrevSolutionToolStripMenuItem.Enabled = false;
                return;
            }

            if (mSolIndex < mSolutions.Count - 1)
            {
                Rectangle rect1, rect2;
                int ioldsol = mSolIndex;
                
                mSolIndex++;

                System.Diagnostics.Trace.WriteLine("Invalidate from GotoNextSolution()");

                GetSolTilePair(ioldsol, out rect1, out rect2);
                Invalidate(rect1);
                Invalidate(rect2);
                GetSolTilePair(mSolIndex, out rect1, out rect2);
                Invalidate(rect1);
                Invalidate(rect2);
            }
            if (mSolIndex >= mSolutions.Count - 1)
            {
                mSolIndex = mSolutions.Count - 1;
            }

            FirstSolutionToolStripMenuItem.Enabled = (mSolIndex > 0);
            NextSolutionToolStripMenuItem.Enabled = (mSolIndex < mSolutions.Count - 1);
            PrevSolutionToolStripMenuItem.Enabled = (mSolIndex > 0);

        }

        private void GotoPrevSolution()
        {
            if (mSolutionImage == null || mSolutions == null || mSolutions.Count == 0)
            {
                FirstSolutionToolStripMenuItem.Enabled = false;
                NextSolutionToolStripMenuItem.Enabled = false;
                PrevSolutionToolStripMenuItem.Enabled = false;
                return;
            }

            if (mSolIndex > 0)
            {
                Rectangle rect1, rect2;
                int ioldsol = mSolIndex;
                
                mSolIndex--;

                System.Diagnostics.Trace.WriteLine("Invalidate from GotoPrevSolution()");

                GetSolTilePair(ioldsol, out rect1, out rect2);
                Invalidate(rect1);
                Invalidate(rect2);
                GetSolTilePair(mSolIndex, out rect1, out rect2);
                Invalidate(rect1);
                Invalidate(rect2);
            }
            if (mSolIndex <= 0)
            {
                mSolIndex = 0;
            }

            FirstSolutionToolStripMenuItem.Enabled = (mSolIndex > 0);
            PrevSolutionToolStripMenuItem.Enabled = (mSolIndex > 0);
            NextSolutionToolStripMenuItem.Enabled = (mSolIndex < mSolutions.Count - 1);
        }

        private void GotoFirstSolution()
        {
            FirstSolutionToolStripMenuItem.Enabled = false;
            PrevSolutionToolStripMenuItem.Enabled = false;
            if (mSolutionImage == null || mSolutions == null || mSolutions.Count == 0)
            {
                NextSolutionToolStripMenuItem.Enabled = false;
                return;
            }

            int ioldsol = mSolIndex;
            mSolIndex = 0;

            NextSolutionToolStripMenuItem.Enabled = (mSolIndex < mSolutions.Count - 1);

            if (ioldsol == mSolIndex)
            {
                return;
            }

            System.Diagnostics.Trace.WriteLine("Invalidate from GotoFirstSolution()");

            Rectangle rect1, rect2;
            GetSolTilePair(ioldsol, out rect1, out rect2);
            Invalidate(rect1);
            Invalidate(rect2);
            GetSolTilePair(mSolIndex, out rect1, out rect2);
            Invalidate(rect1);
            Invalidate(rect2);
        }

    /* Event handlers */

        private void MainForm_Load(object sender, EventArgs e)
        {
            /* settings */

            Properties.Settings settings = new Properties.Settings();

            this.Width = kIniWidth;
            this.Height = kIniHeight;
            mNewWidth = mNewHeight = -1;

            mResponsiveToMouse = settings.MouseEnabled;
            NoahEnabledToolStripMenuItem.Checked = settings.NoahEnabled;
            DemoToolStripMenuItem.Enabled = settings.NoahEnabled;

            FirstSolutionToolStripMenuItem.Enabled = false;
            PrevSolutionToolStripMenuItem.Enabled = false;
            NextSolutionToolStripMenuItem.Enabled = false;

            /* state setting */
            mState = settings.NoahEnabled ? State.Idle : State.Disabled;

            /* the issue handler module for the core functionality */
            mHandler = new IssueHandler();

            /* display */
            mDisplayBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);

            /* clipboard */
            RegisterClipboardViewer();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            UnregisterClipboardViewer();
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Properties.Settings settings = new Properties.Settings();
            settings.NoahEnabled = NoahEnabledToolStripMenuItem.Checked;
            settings.Save();

            mHandler.Dispose();
        }

        private void MainForm_Activated(object sender, EventArgs e)
        {
        }

        private void MainForm_Paint(object sender, PaintEventArgs e)
        {
            System.Diagnostics.Trace.WriteLine("Enter Paint()");

            if (mNewWidth > 0)
            {
                this.Width = mNewWidth;
                this.Height = mNewHeight;
                mNewWidth = -1;
                mNewHeight = -1;
            }

            Graphics gForm = e.Graphics;
            Graphics g = Graphics.FromImage(mDisplayBuffer);

            g.Clear(Color.Black);

            /* output something and update status bar */
            switch (mState)
            {
                case State.Idle:
                    MainStatusLabel.Text = "Ready";

                    g.DrawString("Capturing image ...",
                        new Font(SystemFonts.DefaultFont.FontFamily, 16),
                        Brushes.White, 10, 10 + TopBarHeight);
                    g.DrawString("Press Alt+PrtSc over the web browser",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 20, 50 + TopBarHeight);
                    g.DrawString("containing the game",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 30, 70 + TopBarHeight);
                    g.DrawString("Set this window to proper size",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 20, 100 + TopBarHeight);
                    g.DrawString("after a successful response",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 30, 120 + TopBarHeight);
                    break;
                case State.Failed:
                    MainStatusLabel.Text = "Failed";

                    g.DrawString("Oops! Failed in capturing",
                        new Font(SystemFonts.DefaultFont.FontFamily, 16),
                        Brushes.White, 10, 10 + TopBarHeight);

                    g.DrawString("Probably no game grid is detected",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 20, 50 + TopBarHeight);
                    break;
                case State.NoSolution:
                    MainStatusLabel.Text = "No Solution";

                    g.DrawString("No solution was found",
                        new Font(SystemFonts.DefaultFont.FontFamily, 16),
                        Brushes.White, 10, 10 + TopBarHeight);
                    break;
                case State.Disabled:
                    MainStatusLabel.Text = "Disabled";

                    g.DrawString("Capturing disabled",
                        new Font(SystemFonts.DefaultFont.FontFamily, 16),
                        Brushes.White, 10, 10 + TopBarHeight);
                    g.DrawString("Click File->Enabled to enable",
                        new Font(SystemFonts.DefaultFont.FontFamily, 10),
                        Brushes.White, 20, 50 + TopBarHeight);
                    break;
                case State.Done:
                    MainStatusLabel.Text = String.Format("Solution {0}/{1}",
                        mSolIndex + 1, mSolutions.Count);
                    break;
            }

            if (mState == State.Done && mSolutions != null && mSolutionImage != null)
            {
                g.DrawImage(mSolutionImage, 0, TopBarHeight);

                if (mSolutions.Count > 0)
                {
                    int x0, y0, x1, y1, w, h;
                    GetSolTilePair(mSolIndex, out x0, out y0, out x1, out y1, out w, out h);

                    int edge = (kFrameBorderWidth + 1) / 2;
                    int dminus = kFrameBorderWidth + kFrameBorderWidth % 2;
                    g.DrawRectangle(new Pen(Color.Red, kFrameBorderWidth), x0 + edge, y0 + edge,
                        w - dminus, h - dminus);
                    g.DrawRectangle(new Pen(Color.Red, kFrameBorderWidth), x1 + edge, y1 + edge,
                        w - dminus, h - dminus);
                }

                if (!this.MaximizeBox)
                {
                    this.MaximizeBox = true;
                    this.FormBorderStyle = FormBorderStyle.Sizable;
                }
            }

            gForm.DrawImage(mDisplayBuffer, 0, 0);

            System.Diagnostics.Trace.WriteLine("Exit Paint()");
        }   /* MainForm_Paint */

        private void PrevSolutionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GotoPrevSolution();
        }

        private void NextSolutionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GotoNextSolution();
        }

        private void ExitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void FirstSolutionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            GotoFirstSolution();
        }

        void SetToIdle()
        {
            if (mState == State.Disabled)
            {
                RegisterClipboardViewer();
            }

            mState = State.Idle;
            NoahEnabledToolStripMenuItem.Checked = true;
            DemoToolStripMenuItem.Enabled = true;

            this.Width = kIniWidth;
            this.Height = kIniHeight;

            mSolutionImage = null;
            mSolIndex = 0;
            mSolutions = null;

            System.Diagnostics.Trace.WriteLine("Invalidate from SetToIdle()");

            UpdateControls();
        }

        void SetToDisabled()
        {
            UnregisterClipboardViewer();

            mState = State.Disabled;
            NoahEnabledToolStripMenuItem.Checked = false;
            DemoToolStripMenuItem.Enabled = false;

            this.Width = kIniWidth;
            this.Height = kIniHeight;

            mSolutionImage = null;
            mSolIndex = 0;
            mSolutions = null;

            System.Diagnostics.Trace.WriteLine("Invalidate from SetToDisabled()");

            UpdateControls();
        }

        private void AboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MainAboutBox aboutbox = new MainAboutBox();
            aboutbox.ShowDialog(this);
        }

        private void MainForm_Resize(object sender, EventArgs e)
        {
            if (this.ClientSize.Width > 0 && this.ClientSize.Height > 0)
            {
                mDisplayBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            }
        }

        private void MainForm_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Space: case Keys.PageDown: case Keys.Right: case Keys.Down:
                    /* 32 / 34 / 39 / 40 */
                    GotoNextSolution();
                    break;
                case Keys.PageUp: case Keys.Left: case Keys.Up:
                    /* 33 / 37 / 38 */
                    GotoPrevSolution();
                    break;
                case Keys.Back: case Keys.Home:
                    /* 8 / 36 */
                    GotoFirstSolution();
                    break;
            }
        }


        protected override void WndProc(ref Message m)
        {
            switch ((Msgs)m.Msg)
            {
                case Msgs.WM_DRAWCLIPBOARD:
                    Serve();
                    User32.SendMessage(mClipboardViewerNext, m.Msg, m.WParam, m.LParam);
                    break;
                case Msgs.WM_CHANGECBCHAIN:
                    /*
                     * <remarks>
                     *  WParam is the handle to the window being removed
                     *  LParam is the handle to the next window in the chain following
                     *  the one being removed
                     *  
                     *  So when the window next to this one is being removed
                     *  the next clipboard viewer should be updated to the one 
                     *  following the window being removed, i.e. indicated by LParam;
                     *  otherwise simply pass the messege over to the next window
                     *  in the chain
                     * </remarks>
                     */
                    if (m.WParam == mClipboardViewerNext)
                    {
                        mClipboardViewerNext = m.LParam;
                    }
                    else
                    {
                        User32.SendMessage(mClipboardViewerNext, m.Msg, m.WParam, m.LParam);
                    }
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        private void MainForm_MouseClick(object sender, MouseEventArgs e)
        {
        }

        private void MainForm_MouseDoubleClick(object sender, MouseEventArgs e)
        {
        }

        private void MainForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (!mResponsiveToMouse)
            {
                return;
            }
            if (e.Button == MouseButtons.Left)
            {
                GotoNextSolution();
            }
            else if (e.Button == MouseButtons.Right)
            {
                GotoPrevSolution();
            }
        }

        private void OptionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OptionsForm opform = new OptionsForm();
            opform.MouseEnabled = mResponsiveToMouse;
            opform.ShowDialog(this);
            mResponsiveToMouse = opform.MouseEnabled;
        }

        private void DemoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Image image = Properties.Resources.Demo;
            Clipboard.SetImage(image);
        }

        private void OnlineHelpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            String url = "http://docs.google.com/View?id=dc5txnhj_1294mjqbwf2";
            System.Diagnostics.Process.Start(url);
        }

        private void CloseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (mState == State.Disabled)
            {
                return;
            }
            SetToIdle();
        }

        private void NoahEnabledToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (NoahEnabledToolStripMenuItem.Checked)
            {
                if (mState == State.Disabled)
                {
                    SetToIdle();
                }
            }
            else
            {
                SetToDisabled();
            }
        }
    }
}
